package com.github.chrisdchristo.capsule;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactResult;
import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResolutionException;
import org.eclipse.aether.resolution.VersionRangeResult;
import java.io.*;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.jar.*;
import java.util.zip.ZipEntry;
/**
* Mojo to generate a Capsule jar
*/
@org.apache.maven.plugins.annotations.Mojo(name = "build", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyCollection = ResolutionScope.TEST, requiresDependencyResolution
= ResolutionScope.RUNTIME_PLUS_SYSTEM)
public class CapsuleMojo extends Mojo {
public final String pluginKey() {
return "com.github.chrisdchristo:capsule-maven-plugin";
}
public final String logPrefix() {
return "[CapsuleMavenPlugin] ";
}
private static final String DEFAULT_CAPSULE_VERSION = "1.0.3";
private static final String DEFAULT_CAPSULE_MAVEN_VERSION = "1.0.3";
private static final String CAPSULE_GROUP = "co.paralleluniverse";
private static final String DEFAULT_CAPSULE_NAME = "Capsule";
private static final String DEFAULT_CAPSULE_CLASS = DEFAULT_CAPSULE_NAME + ".class";
private static final String DEFAULT_CAPSULE_MAVEN_NAME = "MavenCapsule";
private static final String DEFAULT_CAPSULE_MAVEN_CLASS = "MavenCapsule.class";
private static final String EXEC_PREFIX = "#!/bin/sh\n\nexec java -jar \"$0\" \"$@\"\n\n";
private static final String EXEC_TRAMPOLINE_PREFIX = "#!/bin/sh\n\nexec java -Dcapsule.trampoline -jar \"$0\" \"$@\"\n\n";
private static final String EXEC_PLUGIN_KEY = "org.codehaus.mojo:exec-maven-plugin";
/**
* OPTIONAL VARIABLES
*/
@Parameter(property = "capsule.outputDir", defaultValue = "${project.build.directory}")
private File outputDir = null;
@Parameter(property = "capsule.version")
private String capsuleVersion = DEFAULT_CAPSULE_VERSION;
@Parameter(property = "capsule.maven.version")
private String capsuleMavenVersion = DEFAULT_CAPSULE_MAVEN_VERSION;
@Parameter(property = "capsule.appClass")
private String appClass = null;
@Parameter(property = "capsule.caplets")
private String caplets;
@Parameter(property = "capsule.type")
private Type type = null;
@Parameter(property = "capsule.chmod")
private boolean chmod = false;
@Parameter(property = "capsule.trampoline")
private boolean trampoline = false;
@Parameter(property = "capsule.setManifestRepos")
private boolean setManifestRepos = false;
@Parameter(property = "capsule.includeApp")
private boolean includeApp = true;
@Parameter(property = "capsule.includeAppDep")
private boolean includeAppDep = false;
@Parameter(property = "capsule.includePluginDep")
private boolean includePluginDep = false;
@Parameter(property = "capsule.includeTransitiveDep")
private boolean includeTransitiveDep = false;
@Parameter(property = "capsule.includeCompileDep")
private boolean includeCompileDep = false;
@Parameter(property = "capsule.includeRuntimeDep")
private boolean includeRuntimeDep = false;
@Parameter(property = "capsule.includeProvidedDep")
private boolean includeProvidedDep = false;
@Parameter(property = "capsule.includeSystemDep")
private boolean includeSystemDep = false;
@Parameter(property = "capsule.includeTestDep")
private boolean includeTestDep = false;
@Parameter(property = "capsule.includeOptionalDep")
private boolean includeOptionalDep = false;
@Parameter(property = "capsule.resolveApp")
private boolean resolveApp = false;
@Parameter(property = "capsule.resolveAppDep")
private boolean resolveAppDep = false;
@Parameter(property = "capsule.resolvePluginDep")
private boolean resolvePluginDep = false;
@Parameter(property = "capsule.resolveTransitiveDep")
private boolean resolveTransitiveDep = false;
@Parameter(property = "capsule.resolveCompileDep")
private boolean resolveCompileDep = false;
@Parameter(property = "capsule.resolveRuntimeDep")
private boolean resolveRuntimeDep = false;
@Parameter(property = "capsule.resolveProvidedDep")
private boolean resolveProvidedDep = false;
@Parameter(property = "capsule.resolveSystemDep")
private boolean resolveSystemDep = false;
@Parameter(property = "capsule.resolveTestDep")
private boolean resolveTestDep = false;
@Parameter(property = "capsule.resolveOptionalDep")
private boolean resolveOptionalDep = false;
@Parameter(property = "capsule.execPluginConfig")
private String execPluginConfig = null;
@Parameter(property = "capsule.fileName")
private String fileName = null;
@Parameter(property = "capsule.fileDesc")
private String fileDesc = "-capsule";
@Parameter
private Pair<String, String>[] properties = null; // System-Properties for the app
@Parameter
private Pair<String, String>[] manifest = null; // additional manifest entries
@Parameter
private Mode[] modes = null; // modes for specific properties and manifest entries
@Parameter
private FileSet[] fileSets = null; // assembly style filesets to add to the capsule
@Parameter
private DependencySet[] dependencySets = null; // assembly style dependency sets to add to the capsule
// will be loaded when run
private Map<String, File> capletFiles = new HashMap<>();
private Xpp3Dom execConfig = null;
private File resolvedCapsuleProjectFile = null;
private File resolvedCapsuleMavenProjectFile = null;
private String outputName;
@Override
public void execute() throws MojoExecutionException, MojoFailureException {
// check for type (this overrides custom behaviour)
if (type == Type.empty) {
includeApp = false;
includeAppDep = false;
includePluginDep = false;
includeTransitiveDep = false;
includeCompileDep = false;
includeRuntimeDep = false;
includeProvidedDep = false;
includeSystemDep = false;
includeTestDep = false;
includeOptionalDep = false;
resolveApp = true;
resolveAppDep = true;
resolvePluginDep = true;
resolveTransitiveDep = true;
resolveCompileDep = true;
resolveRuntimeDep = true;
resolveProvidedDep = false;
resolveSystemDep = false;
resolveTestDep = false;
resolveOptionalDep = false;
} else if (type == Type.thin) {
includeApp = true;
includeAppDep = false;
includePluginDep = false;
includeTransitiveDep = false;
includeCompileDep = false;
includeRuntimeDep = false;
includeProvidedDep = false;
includeSystemDep = false;
includeTestDep = false;
includeOptionalDep = false;
resolveApp = false;
resolveAppDep = true;
resolvePluginDep = true;
resolveTransitiveDep = true;
resolveCompileDep = true;
resolveRuntimeDep = true;
resolveProvidedDep = false;
resolveSystemDep = false;
resolveTestDep = false;
resolveOptionalDep = false;
} else if (type == Type.fat) {
includeApp = true;
includeAppDep = true;
includePluginDep = true;
includeTransitiveDep = true;
includeCompileDep = true;
includeRuntimeDep = true;
includeProvidedDep = false;
includeSystemDep = false;
includeTestDep = false;
includeOptionalDep = false;
resolveApp = false;
resolveAppDep = false;
resolvePluginDep = false;
resolveTransitiveDep = false;
resolveCompileDep = false;
resolveRuntimeDep = false;
resolveProvidedDep = false;
resolveSystemDep = false;
resolveTestDep = false;
resolveOptionalDep = false;
}
// check for exec plugin
if (execPluginConfig != null && project.getPlugin(EXEC_PLUGIN_KEY) != null) {
final Plugin plugin = project.getPlugin(EXEC_PLUGIN_KEY);
if (execPluginConfig.equals("root")) {
execConfig = (Xpp3Dom) plugin.getConfiguration();
} else {
final List<PluginExecution> executions = plugin.getExecutions();
for (final PluginExecution execution : executions) {
if (execution.getId().equals(execPluginConfig)) {
execConfig = (Xpp3Dom) execution.getConfiguration();
break;
}
}
}
}
// get app class from exec config (but only if app class is not set)
if (appClass == null && execConfig != null) {
final Xpp3Dom mainClassElement = execConfig.getChild("mainClass");
if (mainClassElement != null) appClass = mainClassElement.getValue();
}
// fail if no app class
if (appClass == null)
throw new MojoFailureException(logPrefix() + " appClass not set (or could not be obtained from the exec plugin mainClass)");
// resolve outputDir name (the file name of the capsule jar)
this.outputName = this.fileName != null ? this.fileName : this.finalName;
if (this.fileDesc != null) outputName += this.fileDesc;
// check for caplets existence
if (this.caplets == null) this.caplets = "";
if (!caplets.isEmpty()) {
final StringBuilder capletString = new StringBuilder();
final File classesDir = new File(this.buildDir, "classes");
for (final String caplet : this.caplets.split(" ")) {
try {
Files.walkFileTree(classesDir.toPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) {
if (!attrs.isDirectory() && path.toString().contains(caplet)) {
capletFiles.put(caplet, path.toFile());
return FileVisitResult.TERMINATE;
}
return FileVisitResult.CONTINUE;
}
});
} catch (final IOException e) {
e.printStackTrace();
}
if (!capletFiles.containsKey(caplet))
if (!caplet.contains(":")) // not from repo
warn("Could not find caplet " + caplet + " class, skipping.");
if (capletString.length() > 0) capletString.append(" ");
capletString.append(caplet);
}
caplets = capletString.toString();
}
// if no capsule ver specified, find the latest one
if (capsuleVersion == null) {
final DefaultArtifact artifact = new DefaultArtifact(CAPSULE_GROUP, "capsule", null, null, "[0,)");
final VersionRangeRequest request = new VersionRangeRequest().setRepositories(remoteRepos).setArtifact(artifact);
try {
final VersionRangeResult result = repoSystem.resolveVersionRange(repoSession, request);
// get the latest version that is not a snapshot
for (int i = result.getVersions().size() - 1; i >= 0; i--) {
final String currentVersion = result.getVersions().get(i).toString();
if (!currentVersion.contains("SNAPSHOT")) {
capsuleVersion = result.getVersions().get(i).toString();
break;
}
}
} catch (VersionRangeResolutionException e) {
throw new MojoFailureException(e.getMessage());
}
}
// double check outputDir is not in some undesired locations
final List<String> illegalOutputPaths = Arrays.asList(
this.buildDir.getPath() + File.separatorChar + "classes",
this.buildDir.getPath() + File.separatorChar + "classes/"
);
if (illegalOutputPaths.contains(this.outputDir.getPath())) {
this.outputDir = this.buildDir;
debug("Output was an illegal path, resorting to default build directory.");
}
// build path if doesn't exist
if (!outputDir.exists()) {
boolean success = outputDir.mkdirs();
if (!success) throw new MojoFailureException("Failed to build outputDir path");
}
info("[Capsule Version]: " + capsuleVersion);
info("[Output Directory]: " + outputDir.toString());
info("[Build Info]: " + buildInfoString());
try {
build();
} catch (final IOException e) {
e.printStackTrace();
throw new MojoFailureException(e.getMessage());
}
}
/**
* Build the capsule jar based on the parameters
*/
public void build() throws IOException {
final File jarFile = new File(this.outputDir, this.outputName + ".jar");
if (jarFile.exists()) {
info("EXISTS - " + jarFile.getName() + " (WILL OVERWRITE)");
final boolean deleteResult = jarFile.delete();
if (!deleteResult) {
warn("FAILED TO DELETE - " + jarFile.getName());
}
}
final JarOutputStream jarStream = new JarOutputStream(new FileOutputStream(jarFile));
info("[Capsule Jar File]: " + jarFile.getName());
// add manifest entries
addManifest(jarStream);
// add Capsule.class
addCapsuleClass(jarStream);
// add caplets - i.e custom capsule classes (if exists)
addCapletClasses(jarStream);
// add CapsuleMaven classes (if we need to do any resolving on launch)
addMavenCapletClasses(jarStream);
// add the app jar
addApp(jarStream);
// add the dependencies as embedded jars
addDependencies(jarStream);
// add some files and folders to the capsule from filesets and dependencysets
addFileSets(jarStream);
addDependencySets(jarStream);
IOUtil.close(jarStream);
// build the chmod version of the capsule
addChmodCopy(jarFile);
// build the trampoline version of the capsule
addTrampolineCopy(jarFile);
// attach the capsule as a maven artifact
info("[Maven Artifact]: Attached capsule artifact to maven (" + jarFile.getName() + ").");
helper.attachArtifact(project, jarFile, "capsule");
}
// BUILD PROCESS
private void addManifest(final JarOutputStream jar) throws IOException {
final Manifest manifestBuild = new Manifest();
final Attributes mainAttributes = manifestBuild.getMainAttributes();
mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
mainAttributes.put(Attributes.Name.MAIN_CLASS, DEFAULT_CAPSULE_NAME);
mainAttributes.put(new Attributes.Name("Application-Class"), this.appClass);
mainAttributes.put(new Attributes.Name("Application-Name"), this.outputName);
mainAttributes.put(new Attributes.Name("Premain-Class"), DEFAULT_CAPSULE_NAME);
mainAttributes.put(new Attributes.Name("Build-Info"), buildInfoString());
final String artifactsString = artifactString();
if (!artifactsString.isEmpty())
mainAttributes.put(new Attributes.Name("Embedded-Artifacts"), artifactsString);
final String dependencyString = dependencyString();
if (!dependencyString.isEmpty())
mainAttributes.put(new Attributes.Name("Dependencies"), dependencyString);
final String repoString = repoString().trim();
if (!repoString.isEmpty() && setManifestRepos)
mainAttributes.put(new Attributes.Name("Repositories"), repoString);
// add MavenCapsule caplet (if needed) & others specified by user
if (resolveApp || resolveCompileDep || resolveRuntimeDep || resolveProvidedDep || resolveSystemDep || resolveTestDep)
mainAttributes.put(new Attributes.Name("Caplets"), (DEFAULT_CAPSULE_MAVEN_NAME + " " + this.caplets).trim());
else if (this.caplets != null && !this.caplets.isEmpty())
mainAttributes.put(new Attributes.Name("Caplets"), this.caplets.trim());
// add properties
final String propertiesString = systemPropertiesString();
if (propertiesString != null) mainAttributes.put(new Attributes.Name("System-Properties"), propertiesString);
// get arguments from exec plugin (if exist)
if (execConfig != null) {
final Xpp3Dom argsElement = execConfig.getChild("arguments");
if (argsElement != null) {
final Xpp3Dom[] argsElements = argsElement.getChildren();
if (argsElements != null && argsElements.length > 0) {
final StringBuilder argsList = new StringBuilder();
for (final Xpp3Dom arg : argsElements) {
if (arg != null && arg.getValue() != null)
argsList.append(arg.getValue().replace(" ", "")).append(" ");
}
mainAttributes.put(new Attributes.Name("Args"), argsList.toString());
}
}
}
// custom user defined manifest entries (will override any before)
if (this.manifest != null)
for (final Pair<String, String> entry : this.manifest)
mainAttributes.put(new Attributes.Name(entry.key), entry.value);
// mode sections
if (this.modes != null) {
for (final Mode mode : this.modes) {
if (mode.name == null) warn("Mode defined without name, ignoring.");
else {
final Attributes modeAttributes = new Attributes();
// add manifest entries to the mode section (these entries will override the manifests' main entries if mode is selected at runtime)
if (mode.manifest != null) {
for (final Pair<String, String> entry : mode.manifest)
modeAttributes.put(new Attributes.Name(entry.key), entry.value);
}
// add properties to the mode, this set will override all properties of the previous set.
if (mode.properties != null) {
final StringBuilder modePropertiesList = new StringBuilder();
for (final Pair property : mode.properties)
if (property.key != null && property.value != null) {
modePropertiesList.append(property.key).append("=").append(property.value).append(" ");
}
if (modePropertiesList.length() > 0)
modeAttributes.put(new Attributes.Name("System-Properties"), modePropertiesList.toString());
}
// finally add the mode's properties and manifest entries to its own section.
if (!modeAttributes.isEmpty()) manifestBuild.getEntries().put(mode.name, modeAttributes);
}
}
}
// write to jar
final ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
manifestBuild.write(dataStream);
final byte[] bytes = dataStream.toByteArray();
final ByteArrayInputStream manifestInputStream = new ByteArrayInputStream(bytes);
printManifest(manifestBuild);
addToJar(JarFile.MANIFEST_NAME, manifestInputStream, jar);
}
private void addCapsuleClass(final JarOutputStream jar) throws IOException {
final JarInputStream capsuleJarInputStream = new JarInputStream(new FileInputStream(resolveCapsule()));
JarEntry entry;
while ((entry = capsuleJarInputStream.getNextJarEntry()) != null) // look for Capsule.class
if (entry.getName().equals(DEFAULT_CAPSULE_CLASS))
addToJar(DEFAULT_CAPSULE_CLASS, new ByteArrayInputStream(IOUtil.toByteArray(capsuleJarInputStream)), jar);
}
private void addCapletClasses(final JarOutputStream jar) throws IOException {
if (caplets != null && !caplets.isEmpty()) {
for (final Map.Entry<String, File> caplet : this.capletFiles.entrySet()) {
final String path = caplet.getValue().getPath();
addToJar(path.substring(path.indexOf("classes") + 8), new FileInputStream(caplet.getValue()), jar);
info("\t[Caplet] Embedded Caplet class " + caplet.getKey() + " from " + caplet.getValue());
}
}
}
private void addMavenCapletClasses(final JarOutputStream jar) throws IOException {
if (resolveApp || resolveCompileDep || resolveRuntimeDep || resolveProvidedDep || resolveSystemDep || resolveTestDep) {
// get capsule maven classes
final JarInputStream capsuleJarInputStream = new JarInputStream(new FileInputStream(resolveCapsuleMaven()));
JarEntry entry;
while ((entry = capsuleJarInputStream.getNextJarEntry()) != null) {
if (entry.getName().contains("capsule") || entry.getName().equals(DEFAULT_CAPSULE_MAVEN_CLASS)) {
addToJar(entry.getName(), new ByteArrayInputStream(IOUtil.toByteArray(capsuleJarInputStream)), jar);
}
}
info("\t[Maven Caplet] Embedded Maven Caplet classes v" + capsuleMavenVersion + " (so capsule can resolve at launch)");
}
}
private void addApp(final JarOutputStream jar) throws IOException {
if (includeApp) {
try {
final File mainJarFile = new File(this.buildDir, this.finalName + ".jar");
addToJar(mainJarFile.getName(), new FileInputStream(mainJarFile), jar);
info("\t[App] App jar embedded (" + mainJarFile.getName() + ")");
} catch (final FileNotFoundException e) { // if project jar wasn't built (perhaps the mvn package wasn't run, and only the mvn compile was run)
// add compiled project classes instead
warn("\t[App] Couldn't add main jar file to fat capsule, adding the project classes directly instead.");
final File classesDir = new File(this.buildDir, "classes");
Files.walkFileTree(classesDir.toPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(final Path path, final BasicFileAttributes attrs) throws IOException {
if (!attrs.isDirectory() && !path.endsWith(".DS_Store") && !path.endsWith("MANIFEST.MF")) {
addToJar(path.toString().substring(path.toString().indexOf("classes") + 8), new FileInputStream(path.toFile()), jar);
debug("\t\t[App] Adding Compile Project Class to Capsule: [" + path.toFile().getPath() + "]");
}
return FileVisitResult.CONTINUE;
}
});
info("\t[App] App class files embedded.");
}
} else if (resolveApp) {
info("\t[App] App jar NOT embedded and marked to be resolved at launch.");
} else {
warn("\t[App] App jar NOT embedded and NOT marked to be resolved at launch.");
}
}
private void addDependencies(final JarOutputStream jar) throws IOException {
// go through dependencies
final Set<Artifact> artifacts = includeTransitiveDep ? includedDependencyArtifacts() : includedDirectDependencyArtifacts();
for (final Artifact artifact : artifacts) {
final String scope = artifact.getScope() == null || artifact.getScope().isEmpty() ? "compile" : artifact.getScope();
boolean optionalMatch = true;
if (artifact.isOptional()) optionalMatch = includeOptionalDep;
// check artifact has a file
if (artifact.getFile() == null)
warn("\t[Dependency] " + coords(artifact) + "(" + artifact.getScope() + ") file not found, thus will not be added to capsule jar.");
// ignore capsule jar
if (artifact.getGroupId().equalsIgnoreCase(CAPSULE_GROUP) && artifact.getArtifactId().equalsIgnoreCase(DEFAULT_CAPSULE_NAME))
continue;
// check against requested scopes
if (
(includeCompileDep && scope.equals("compile") && optionalMatch) ||
(includeRuntimeDep && scope.equals("runtime") && optionalMatch) ||
(includeProvidedDep && scope.equals("provided") && optionalMatch) ||
(includeSystemDep && scope.equals("system") && optionalMatch) ||
(includeTestDep && scope.equals("test") && optionalMatch)
) {
addToJar(artifact.getFile().getName(), new FileInputStream(artifact.getFile()), jar);
info("\t[Embedded-Dependency] " + coords(artifact) + "(" + scope + ")");
} else
debug("\t[Dependency] " + coords(artifact) + "(" + artifact.getScope() + ") skipped, as it does not match any required scope");
}
}
private void addFileSets(final JarOutputStream jar) throws IOException {
if (fileSets == null) return;
for (final FileSet fileSet : fileSets) {
if (fileSet.directory != null && !fileSet.directory.isEmpty()) {
final File fileSetDir = new File(fileSet.directory);
final File directory;
if (fileSetDir.isAbsolute()) {
directory = fileSetDir;
} else {
directory = new File(baseDir.getPath() + File.separatorChar + fileSet.directory);
}
// warn & skip if not directory
if (!directory.isDirectory()) {
warn("[FileSet] Attempted to include file from non-directory [" + directory.getAbsolutePath() + "], skipping...");
continue;
}
final String outputDirectory = addDirectoryToJar(jar, fileSet.outputDirectory);
final Set<File> matchedEntries = new HashSet<>();
// get files entries based on direct files under dir (i.e ignore un sub dirs)
final Set<File> entries = new HashSet<>();
final File[] files = directory.listFiles();
if (files != null) {
for (final File file : files) {
if (!file.isDirectory())
entries.add(file);
}
}
for (final File entry : entries) {
System.out.println(entry.toString());
}
for (final String include : fileSet.includes) {
if (include.contains("*")) { // wildcard
// quick hack to find number of wildcards
final int starCount = include.length() - include.replace("*", "").length();
// max one wildcard allowed
if (starCount > 1) {
warn("\t[FileSet]: More than one asterisk (*) found in include, skipping... | " + include);
continue;
}
// if start
if (include.startsWith("*")) {
final String toMatch = include.substring(1);
for (final File entry : entries) {
if (entry.getName().endsWith(toMatch)) {
matchedEntries.add(entry);
}
}
}
// if end
else if (include.endsWith("*")) {
final String toMatch = include.substring(0, include.length() - 1);
for (final File entry : entries) {
if (entry.getName().startsWith(toMatch)) {
matchedEntries.add(entry);
}
}
}
// if middle (check start and end match)
else {
final String[] split = include.split("\\*");
for (final File entry : entries) {
if (entry.getName().startsWith(split[0]) && entry.getName().endsWith(split[1])) {
matchedEntries.add(entry);
}
}
}
} else { // match exact (no wildcard)
matchedEntries.add(new File(directory, include));
}
// add all entries matched
if (!matchedEntries.isEmpty()) {
for (final File entry : matchedEntries) {
addToJar(outputDirectory + entry.getName(), new FileInputStream(entry), jar);
info("\t[FileSet]: Embedded " + outputDirectory + entry.getName() + " from " + directory);
}
} else {
warn("\t[FileSet]: No matches found in " + directory);
}
}
}
}
}
private void addDependencySets(final JarOutputStream jar) throws IOException {
if (dependencySets == null) return;
for (final DependencySet dependencySet : dependencySets) {
final Artifact artifact = toArtifact(resolve(dependencySet.toString()));
if (artifact == null || artifact.getFile() == null) {
warn("\t[DependencySet]: Resolution Fail | " + dependencySet.toString());
continue;
}
final JarFile jarFile = new JarFile(artifact.getFile());
final Set<JarEntry> entries = set(jarFile.entries());
final String outputDirectory = addDirectoryToJar(jar, dependencySet.outputDirectory);
// if includes is set add only specified
if (dependencySet.includes != null && dependencySet.includes.length > 0) {
final Set<ZipEntry> matchedEntries = new HashSet<>();
for (final String include : dependencySet.includes) {
if (include.contains("*")) { // wildcard
// quick hack to find number of wildcards
final int starCount = include.length() - include.replace("*", "").length();
// max one wildcard allowed
if (starCount > 1) {
warn("\t[DependencySet]: More than one asterisk (*) found in include, skipping... | " + include);
continue;
}
// if start
if (include.startsWith("*")) {
final String toMatch = include.substring(1);
for (final ZipEntry entry : entries) {
if (entry.getName().endsWith(toMatch)) {
matchedEntries.add(entry);
}
}
}
// if end
else if (include.endsWith("*")) {
final String toMatch = include.substring(0, include.length() - 1);
for (final ZipEntry entry : entries) {
if (entry.getName().startsWith(toMatch)) {
matchedEntries.add(entry);
}
}
}
// if middle (check start and end match)
else {
final String[] split = include.split("\\*");
for (final ZipEntry entry : entries) {
if (entry.getName().startsWith(split[0]) && entry.getName().endsWith(split[1])) {
matchedEntries.add(entry);
}
}
}
} else { // match exact (no wildcard)
matchedEntries.add(jarFile.getEntry(include));
}
}
// add all entries matched
if (!matchedEntries.isEmpty()) {
for (final ZipEntry entry : matchedEntries) {
addToJar(outputDirectory + entry.getName(), jarFile.getInputStream(entry), jar);
info("\t[DependencySet]: Embedded from " + coords(artifact) + " > " + outputDirectory + entry.getName());
}
} else {
warn("\t[DependencySet]: No matches found in " + artifact.getFile());
}
// else add whole file
} else {
if (!dependencySet.unpack) {
info("\t[DependencySet]: Adding " + artifact.getFile().getName() + " to " + outputDirectory);
addToJar(outputDirectory + artifact.getFile().getName(), new FileInputStream(artifact.getFile()), jar);
} else {
if (artifact.getType() != null && artifact.getType().equals("jar")) {
info("\t[DependencySet]: Adding (unpacked) " + artifact.getFile().getName() + " to " + outputDirectory);
for (final ZipEntry entry : entries) {
debug("\t\t[DependencySet]: Adding (unpacked) " + outputDirectory + entry.getName());
addToJar(outputDirectory + entry.getName(), jarFile.getInputStream(entry), jar);
}
} else {
warn("\t[DependencySet]: Cannot unpack " + artifact.getFile().getName() + " as it is not in jar format.");
}
}
}
}
}
private void addChmodCopy(final File jar) throws IOException {
if (this.chmod) {
final File file = createExecCopyProcess(jar, EXEC_PREFIX, ".x");
info("[Capsule CHMOD]: " + file.getName());
}
}
private void addTrampolineCopy(final File jar) throws IOException {
if (this.trampoline) {
final File file = createExecCopyProcess(jar, EXEC_TRAMPOLINE_PREFIX, ".tx");
info("[Capsule Trampoline]: " + file.getName());
}
}
// STRINGS
private String buildInfoString() {
final StringBuilder builder = new StringBuilder();
if (includeApp) builder.append("includeApp ");
if (includeAppDep) builder.append("includeAppDep ");
if (includePluginDep) builder.append("includePluginDep ");
if (includeCompileDep) builder.append("includeCompileDep ");
if (includeRuntimeDep) builder.append("includeRuntimeDep ");
if (includeProvidedDep) builder.append("includeProvidedDep ");
if (includeSystemDep) builder.append("includeSystemDep ");
if (includeTestDep) builder.append("includeTestDep ");
if (includeTransitiveDep) builder.append("includeTransitiveDep ");
if (resolveApp) builder.append("resolveApp ");
if (resolveAppDep) builder.append("resolveAppDep ");
if (resolvePluginDep) builder.append("resolvePluginDep ");
if (resolveCompileDep) builder.append("resolveCompileDep ");
if (resolveRuntimeDep) builder.append("resolveRuntimeDep ");
if (resolveProvidedDep) builder.append("resolveProvidedDep ");
if (resolveSystemDep) builder.append("resolveSystemDep ");
if (resolveTestDep) builder.append("resolveTestDep ");
if (resolveTransitiveDep) builder.append("resolveTransitiveDep ");
return builder.toString().trim();
}
private String repoString() {
final StringBuilder repoList = new StringBuilder();
for (final RemoteRepository repository : this.remoteRepos)
repoList.append(repository.getId()).append("(").append(repository.getUrl()).append(") ");
return repoList.toString();
}
private String artifactString() throws IOException {
final StringBuilder artifactList = new StringBuilder();
if (includeApp) artifactList.append(coords(project.getArtifact())).append(" ");
// go through artifacts
final Set<Dependency> dependencies = includeTransitiveDep ? includedDependencies() : includedDirectDependencies();
for (final Dependency dependency : dependencies) {
final String scope = dependency.getScope() == null || dependency.getScope().isEmpty() ? "compile" : dependency.getScope();
boolean optionalMatch = true;
if (dependency.isOptional()) optionalMatch = includeOptionalDep;
// ignore capsule jar
if (dependency.getGroupId().equalsIgnoreCase(CAPSULE_GROUP) && dependency.getArtifactId().equalsIgnoreCase(DEFAULT_CAPSULE_NAME))
continue;
// check against requested scopes
if (
(includeCompileDep && scope.equals("compile") && optionalMatch) ||
(includeRuntimeDep && scope.equals("runtime") && optionalMatch) ||
(includeProvidedDep && scope.equals("provided") && optionalMatch) ||
(includeSystemDep && scope.equals("system") && optionalMatch) ||
(includeTestDep && scope.equals("test") && optionalMatch)
)
artifactList.append(coordsWithExclusions(dependency)).append(" ");
}
return artifactList.toString();
}
private String dependencyString() throws IOException {
final StringBuilder dependenciesList = new StringBuilder();
// add app to be resolved
if (resolveApp)
dependenciesList.append(coords(this.project.getArtifact())).append(" ");
// go through dependencies
final Set<Dependency> dependencies = resolveTransitiveDep ? resolvedDependencies() : resolvedDirectDependencies();
for (final Dependency dependency : dependencies) {
final String scope = dependency.getScope() == null || dependency.getScope().isEmpty() ? "compile" : dependency.getScope();
boolean optionalMatch = true;
if (dependency.isOptional()) optionalMatch = resolveOptionalDep;
// ignore capsule jar
if (dependency.getGroupId().equalsIgnoreCase(CAPSULE_GROUP) && dependency.getArtifactId().equalsIgnoreCase(DEFAULT_CAPSULE_NAME))
continue;
// check against requested scopes
if (
(resolveCompileDep && scope.equals("compile") && optionalMatch) ||
(resolveRuntimeDep && scope.equals("runtime") && optionalMatch) ||
(resolveProvidedDep && scope.equals("provided") && optionalMatch) ||
(resolveSystemDep && scope.equals("system") && optionalMatch) ||
(resolveTestDep && scope.equals("test") && optionalMatch)
)
dependenciesList.append(coordsWithExclusions(dependency)).append(" ");
}
return dependenciesList.toString();
}
private String systemPropertiesString() {
StringBuilder propertiesList = null;
if (this.properties != null) {
propertiesList = new StringBuilder();
for (final Pair property : this.properties) {
if (property.key != null) {
propertiesList.append(property.key);
if (property.value != null && (property.value instanceof String && !((String) property.value).isEmpty()))
propertiesList.append("=").append(property.value);
propertiesList.append(" ");
}
}
} else if (execConfig != null) { // else try and find properties in the exec plugin
propertiesList = new StringBuilder();
final Xpp3Dom propertiesElement = execConfig.getChild("systemProperties");
if (propertiesElement != null) {
final Xpp3Dom[] propertiesElements = propertiesElement.getChildren();
if (propertiesElements != null && propertiesElements.length > 0) {
for (final Xpp3Dom propertyElement : propertiesElements) {
final Xpp3Dom key = propertyElement.getChild("key");
final Xpp3Dom value = propertyElement.getChild("value");
if (key != null && key.getValue() != null) {
propertiesList.append(key.getValue()).append("=");
if (value != null && value.getValue() != null && !value.getValue().isEmpty())
propertiesList.append("=").append(value.getValue());
propertiesList.append(" ");
}
}
}
}
}
return propertiesList == null ? null : propertiesList.toString().trim();
}
private File createExecCopyProcess(final File jar, final String prefix, final String extension) throws IOException {
final File x = new File(jar.getPath().replace(".jar", extension));
if (x.exists()) {
debug("EXISTS - " + x.getName());
return x;
}
FileOutputStream out = null;
FileInputStream in = null;
try {
out = new FileOutputStream(x);
in = new FileInputStream(jar);
out.write((prefix).getBytes("ASCII"));
Files.copy(jar.toPath(), out);
out.flush();
// Runtime.getRuntime().exec("chmod +x " + x.getAbsolutePath());
final boolean execResult = x.setExecutable(true, false);
if (!execResult)
warn("Failed to mark file executable - " + x.getAbsolutePath());
} finally {
IOUtil.close(in);
IOUtil.close(out);
}
return x;
}
// RESOLVERS
private File resolveCapsule() throws IOException {
if (this.resolvedCapsuleProjectFile == null) {
final ArtifactResult artifactResult = this.resolve(CAPSULE_GROUP, "capsule", null, capsuleVersion);
if (artifactResult == null) throw new IOException("Capsule not found from repos");
this.resolvedCapsuleProjectFile = artifactResult.getArtifact().getFile();
}
return this.resolvedCapsuleProjectFile;
}
private File resolveCapsuleMaven() throws IOException {
if (this.resolvedCapsuleMavenProjectFile == null) {
final ArtifactResult artifactResult = this.resolve(CAPSULE_GROUP, "capsule-maven", null, capsuleMavenVersion);
if (artifactResult == null) throw new IOException("CapsuleMaven not found from repos");
this.resolvedCapsuleMavenProjectFile = artifactResult.getArtifact().getFile();
}
return this.resolvedCapsuleMavenProjectFile;
}
private Set<Dependency> includedDependencies() {
return cleanDependencies(appDependencies(), this.includeAppDep, pluginDependencies(), this.includePluginDep);
}
private Set<Dependency> includedDirectDependencies() {
return cleanDependencies(appDirectDependencies(), this.includeAppDep, pluginDirectDependencies(), this.includePluginDep);
}
private Set<Artifact> includedDependencyArtifacts() {
return cleanArtifacts(appDependencyArtifacts(), this.includeAppDep, pluginDependencyArtifacts(), this.includePluginDep);
}
private Set<Artifact> includedDirectDependencyArtifacts() {
return cleanArtifacts(appDirectDependencyArtifacts(), this.includeAppDep, pluginDirectDependencyArtifacts(), this.includePluginDep);
}
private Set<Dependency> resolvedDependencies() {
return cleanDependencies(appDependencies(), this.resolveAppDep, pluginDependencies(), this.resolvePluginDep);
}
private Set<Dependency> resolvedDirectDependencies() {
return cleanDependencies(appDirectDependencies(), this.resolveAppDep, pluginDirectDependencies(), this.resolvePluginDep);
}
// HELPER OBJECTS
public enum Type {
empty, thin, fat;
}
public static class Mode {
private String name = null;
private Pair<String, String>[] properties = null;
private Pair<String, String>[] manifest = null;
}
public static class DependencySet {
public String groupId;
public String artifactId;
public String classifier;
public String version;
public String outputDirectory = "/";
public String[] includes;
public boolean unpack = false; // will unpack file of jar, zip, tar.gz, and tar.bz
public String toString() {
return coords(groupId, artifactId, classifier, version);
}
}
public static class FileSet {
public String directory;
public String outputDirectory;
public String[] includes;
}
}